package com.example.graph;

import java.util.*;

/**
 * 现在你总共有 n 门课需要选，记为 0 到 n-1。
 * 在选修某些课程之前需要一些先修课程。 例如，想要学习课程 0 ，你需要先完成课程 1 ，我们用一个匹配来表示他们: [0,1]
 *  给定课程总量以及它们的先决条件，返回你为了学完所有课程所安排的学习顺序。
 * 可能会有多个正确的顺序，你只要返回一种就可以了。如果不可能完成所有课程，返回一个空数组。
 *
 *  示例 1:
 *  输入: 2, [[1,0]]
 * 输出: [0,1]
 * 解释:总共有 2 门课程。要学习课程 1，你需要先完成课程 0。因此，正确的课程顺序为 [0,1] 。
 *
 *  示例 2:
 *  输入: 4, [[1,0],[2,0],[3,1],[3,2]]
 * 输出: [0,1,2,3] or [0,2,1,3]
 * 解释:总共有 4 门课程。要学习课程 3，你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
 * 因此，一个正确的课程顺序是[0,1,2,3] 。另一个正确的排序是[0,2,1,3] 。
 *    1
 * 0      3
 *    2
 */
public class Leetcode210_FindOrder {
    public static void main(String[] args) {
        int nummCourses = 4;
        int[][] prerequisutes = {{1, 0},{2, 0},{3, 1},{3, 2}};
        System.out.println(Arrays.toString(new Solution().findOrder(nummCourses, prerequisutes)));
    }

    static class Solution {
        /**
         * 拓扑排序(BFS实现)
         * @param numCourses
         * @param prerequisites
         * @return
         */
        public int[] findOrder1(int numCourses, int[][] prerequisites) {
            int[] inDegresses =  new int[numCourses];
            int[] res = new int[numCourses];
            Map<Integer, List<Integer>> adj = new HashMap<>();

            // 初始化邻接表和节点的入度矩阵
            for (int[] prerequisite : prerequisites) {
                adj.computeIfAbsent(prerequisite[1], x -> new ArrayList<>()).add(prerequisite[0]);
                ++inDegresses[prerequisite[0]];
            }

//            System.out.println(Arrays.toString(inDegresses));

//            adj.forEach((k, v) -> {
//                System.out.print(k + " ->");
//                System.out.println(Arrays.toString(v.stream().mapToInt(x -> x).toArray()));
//            });

            Queue<Integer> queue = new LinkedList<>();
            // 初始将入度为0的节点入队
            for (int i = 0; i < numCourses; ++i) {
                if (inDegresses[i] == 0) queue.offer(i);
            }

            int count = 0;
            while (!queue.isEmpty()) {
                int tmpNode = queue.poll();
                res[count++] = tmpNode;

                List<Integer> neighbors = adj.get(tmpNode);
                if (neighbors != null) {
                    for (Integer neighbor : neighbors) {
                        if ((--inDegresses[neighbor]) == 0) // 邻接点的入度等于0时将邻接点入队
                            queue.offer(neighbor);
                    }
                }
            }

            return count == numCourses ? res : new int[] {}; // 如果数组中的元素的个数不等于 numCourses说明不满足拓扑排序
        }


        public int[] findOrder(int numCourses, int[][] prerequisites) {
            return findOrder1(numCourses, prerequisites);
        }
    }
}
